#include <iostream>
#include <set>

//c
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>

int main() {
    //拥有 socketfd, clientfds(多个客户端 fd) ..., 这些 fd 将组成 fd_set, 大小取决于 __fd_mask 大小
    //select 会将传入的 fd_set 拷贝到内核中, 内核进行遍历, 当没有对这些 fd 进行读写操作时, 将阻塞
    //当这些 fd_set 中存在读写操作时, 内核通知主进程继续执行
    //https://mp.weixin.qq.com/s/YdIdoZ_yusVWza1PU7lWaw
    
    /*      server                      |               client
        socket()-> socketfd             |          
                                        |
            clientfd                  <---    (add clientfd)  <--  connect
    fd_set(socketfd, clientfds...)      |               
            select(fd_set)              |
                ↓                       |
        socketfd-->accept()           <---    (socketfd can accept) <-- connect
                                        |
        clientfds-->read()            <---    (clientfd can read)  <--  write
    */

    int socketfd = socket(AF_INET, SOCK_STREAM, 0);
    if (socketfd == -1)
        return -1;

    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERT_PORT);
    inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr);

    if (bind(socketfd, (sockaddr*)(&addr), sizeof(sockaddr_in)) == -1)
        return -1;

    if (listen(socketfd, 4))
        return -1;

    int max = socketfd;

    fd_set fdSet;
    std::set<int> clientfds;
    while (true) {
        FD_ZERO(&fdSet);

        //add socketfd
        FD_SET(socketfd, &fdSet);

        //add clients
        for (auto it = clientfds.begin(); it != clientfds.end(); it++)
            FD_SET(*it, &fdSet);
        
        int ret = select(max + 1, &fdSet, nullptr, nullptr, nullptr);

        if (ret <= 0)
            continue;

        //处理客户端消息
        for (auto it = clientfds.begin(); it != clientfds.end();) {
            if (FD_ISSET(*it, &fdSet) <= 0) {
                it++;
                continue;
            }

            //存在客户端发送消息
            char buf[MAXLINE + 1] = {0};

            //读取消息长度为 0, 即断开连接
            //https://blog.csdn.net/qicheng777/article/details/70229456
            if (read(*it, buf, MAXLINE) == 0) {
                close(*it);
                it = clientfds.erase(it);
                continue;
            }

            std::cout << "client " << *it << ":" << buf << std::endl;
            write(*it, buf, MAXLINE);   //echo
            it++;
        }

        //有客户端连接 socket
        if (FD_ISSET(socketfd, &fdSet)) {
            //获取连接的 client 信息
            sockaddr_in clientAddr;
            socklen_t len(sizeof(sockaddr_in));
            int clientfd = accept(socketfd, (sockaddr*)(&clientAddr), &len);

            if (clientfd <= 0)
                continue;

            clientfds.insert(clientfd);
            max = max > clientfd ? max : clientfd;
        }
    }

    return 0;
}